using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Evolution.Engine.Managers;

namespace Evolution.Engine.Cameras
{
    public class Camera : Evolution.Engine.Interfaces.IUpdateable
    {
        protected Vector3 _Position;
        protected Quaternion _Rotation;
        protected Vector3 _RevolveTarget;
        protected Vector3 _Offset;
        protected Matrix _World;
        protected Matrix _View;
        protected Matrix _Projection;
        protected Viewport _Viewport;
        protected BoundingFrustum myFrustum;
        protected CameraType _Type;
        protected float _MinDepth;
        protected float _MaxDepth;
        protected float _AspectRatio;
        private string _Name;
        private Vector3 _LookatOffset;
        private Matrix _ReflectionViewMatrix;
        
        public Matrix MatrixInverseRotation { get { return Matrix.CreateFromQuaternion(_Rotation) * -1; } }
        public Matrix ReflectionViewMatrix { get { return _ReflectionViewMatrix; } set { _ReflectionViewMatrix = value; } }
        public Vector3 LookatOffset { get { return _LookatOffset; } set { _LookatOffset = value; } }
        public CameraType Type { get { return _Type; } }
        public string Name { get { return _Name; } set { _Name = value; } }
        public Vector3 Position { get { return _Position; } set { _Position = value; } }
        public Quaternion Rotation { get { return _Rotation; } set { _Rotation = value; } }
        public Matrix World { get { return _World; } }
        public Matrix View { get { return _View; } }
        public Matrix ViewProjection { get { return _View * _Projection; } }
        public Matrix Projection { get { return _Projection; } }
        public Viewport Viewport { get { return _Viewport; } }
        public BoundingFrustum Frustum { get { return myFrustum; } }
        public Vector3 RevolveTarget
        {
            get { return _RevolveTarget; }
            set { _RevolveTarget = value + _LookatOffset; }
        }
        public Vector3 CameraOffset { get { return _Offset; } set { _Offset = value; } }
        public CameraType CameraType { get { return _Type; } set { _Type = value; } }
        public float MinDepth { get { return _MinDepth; } set { _MinDepth = value; } }
        public float MaxDepth { get { return _MaxDepth; } set { _MaxDepth = value; } }
        public float AspectRatio { get { return _AspectRatio; } set { _AspectRatio = value; } }

        public Camera(string Name)
        {
            _MinDepth = 0.01f;
            _MaxDepth = 3000f;
            _AspectRatio = 1.33f;
            _World = Matrix.Identity;
            _Name = Name;
            _Rotation = new Quaternion(0, 0, 0, 1);
            _LookatOffset = Vector3.Zero;
        }
        public void Translate(Vector3 distance)
        {
            _Position += Vector3.Transform(distance, Matrix.CreateFromQuaternion(_Rotation));
        }
        public void Rotate(Vector3 axis, float angle)
        {
            axis = Vector3.Transform(axis, Matrix.CreateFromQuaternion(_Rotation));
            _Rotation = Quaternion.Normalize(Quaternion.CreateFromAxisAngle(axis, angle) * _Rotation);
        }
        public void GlobalRotate(Vector3 axis, float angle)
        {
            _Rotation = Quaternion.Normalize(Quaternion.CreateFromAxisAngle(axis, angle) * _Rotation);
        }
        public void Revolve(Vector3 axis, float angle)
        {
            Vector3 revolveAxis = Vector3.Transform(axis, Matrix.CreateFromQuaternion(_Rotation));
            Quaternion rotate = Quaternion.CreateFromAxisAngle(revolveAxis, angle);

            _Position = Vector3.Transform(
                _RevolveTarget - _Position, Matrix.CreateFromQuaternion(rotate)
            ) + _RevolveTarget;

            Rotate(axis, angle);
        }
        public void GlobalRevolve(Vector3 axis, float angle)
        {
            Quaternion rotate = Quaternion.CreateFromAxisAngle(axis, angle);

            _Position = Vector3.Transform(
                _RevolveTarget - _Position, Matrix.CreateFromQuaternion(rotate)
            ) + _RevolveTarget;

            GlobalRotate(axis, angle);
        }
        public virtual void SetViewport(Viewport newViewport)
        {
            _Viewport = newViewport;

            _Viewport.MinDepth = _MinDepth;
            _Viewport.MaxDepth = _MaxDepth;

            _AspectRatio = (float)_Viewport.Width / (float)_Viewport.Height;

            _Projection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.PiOver4,
                _AspectRatio,
                _MinDepth,
                _MaxDepth
            );

            Update(null);
        }
        public virtual void Update(GameTime gameTime)
        {
            _View = Matrix.Invert(
                Matrix.CreateFromQuaternion(_Rotation) *
                Matrix.CreateTranslation(_Position)
            );

            CreateReflectionMatrix();

            myFrustum = new BoundingFrustum(Matrix.Multiply(_View, _Projection));
        }
        protected void CreateReflectionMatrix()
        {
            if (PlayerManager.Instance.Player != null)
            {
                float reflectionCamZCoord = -_Position.Y + 0.1f; // + 2 * EnvironmentManager.Instance.WaterHeight;
                Vector3 reflectionCamPosition = new Vector3(_Position.X, reflectionCamZCoord, _Position.Z);

                float reflectionTargetZCoord = -PlayerManager.Instance.Player.Position.Y + 0.1f; // + 2 * EnvironmentManager.Instance.WaterHeight;
                Vector3 reflectionCamTarget = new Vector3(PlayerManager.Instance.Player.Position.X, reflectionTargetZCoord, PlayerManager.Instance.Player.Position.Z);

                Vector3 forwardVector = reflectionCamTarget - reflectionCamPosition;
                Vector3 sideVector = Vector3.Transform(new Vector3(1, 0, 0), _Rotation);
                Vector3 reflectionCamUp = Vector3.Up; //Vector3.Cross(sideVector, forwardVector);

                _ReflectionViewMatrix = Matrix.CreateLookAt(reflectionCamPosition, reflectionCamTarget, reflectionCamUp);
            }
            else
                _ReflectionViewMatrix = _View;
        }
        public override string ToString()
        {
            return string.Format("Pos: {0} Revolve: {1}", _Position.ToString(), _RevolveTarget.ToString());
        }
        public Vector3 ClampToTerrain(Vector3 newPosition)
        {
            float height = SceneManager.Instance.Terrain.GetHeightAt(newPosition);

            if (newPosition.Y < height)
                return new Vector3(newPosition.X, height, newPosition.Z);
            else
                return newPosition;
        }
    }
}
